﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.IO;

namespace Intellisense
{
    public static class SelectedDataGetter
    {
        public static SelectedTaskInfo GetSelectedTaskInfo(string target, int currentPosition)
        {
            if (string.IsNullOrWhiteSpace(target))
                throw new ArgumentNullException();

            MatchCollection endMatches = MSBuildMatch.GetMatches(target, string.Format(Constants.EndElementPattern, "\\w*"), currentPosition);
            if (endMatches != null && endMatches.Count > 0)
            {
                target = target.Substring(0, endMatches[0].Index);
            }

            //get selected task
            List<SelectedTaskInfo> tasks = GetMatchedTasks(target,currentPosition);
            if (tasks == null || tasks.Count <= 0)
                return null;

            SelectedTaskInfo currentTask = null;
            for (int i = tasks.Count - 1; i >= 0; i--)
            {
                if (tasks[i].TaskName == string.Empty)
                {
                    if (tasks[i].TextSpan.StartPosition < currentPosition && currentPosition <= tasks[i].TextSpan.EndPosition)
                    {
                        currentTask = tasks[i];
                        break;
                    }
                }
                else
                {
                    if (tasks[i].TextSpan.StartPosition < currentPosition && currentPosition < tasks[i].TextSpan.EndPosition)
                    {
                        currentTask = tasks[i];
                        break;
                    }
                }
            }
            if (currentTask == null)
                return null;

            string parentXPath = string.Empty;
            for (int i = 0; i < tasks.Count - 1; i++)
            {
                SelectedTaskInfo task = tasks[i];
                if (!(task.TextSpan.StartPosition == currentTask.TextSpan.StartPosition && currentTask.TextSpan.EndPosition == task.TextSpan.EndPosition && currentTask.TaskName == task.TaskName) &&
                    (task.TextSpan.StartPosition <= currentTask.TextSpan.StartPosition && currentTask.TextSpan.StartPosition < task.TextSpan.EndPosition) &&
                    (task.TextSpan.StartPosition < currentTask.TextSpan.EndPosition && currentTask.TextSpan.EndPosition <= task.TextSpan.EndPosition))
                    parentXPath += "/" + task.TaskName;
            }
            parentXPath = parentXPath.TrimEnd('/');
            if (string.IsNullOrWhiteSpace(parentXPath))
                parentXPath = "/";
            currentTask.ParentXPath = parentXPath;
            return currentTask;
        }

        private static List<SelectedTaskInfo> GetMatchedTasks(string target,int currentPosition)
        {
            if (string.IsNullOrWhiteSpace(target))
                throw new ArgumentNullException();

            MatchCollection matches = MSBuildMatch.GetMatches(target, Constants.StartElementPattern, 0);

            //get exclude element
            List<TextSpan> excludeSpans = GetExcludeSpan(target,true);

            Match startTagOnlyMatch = MSBuildMatch.FindLastMatch(target, Constants.StartElementTagPattern, currentPosition, true);
            SelectedTaskInfo lastStartElementTaskInfo = null;
            if (startTagOnlyMatch != null)
            {
                TextSpan tagSpan = new TextSpan { StartPosition = startTagOnlyMatch.Index, EndPosition = startTagOnlyMatch.Index + startTagOnlyMatch.Value.Length };
                bool isNotStartTagOnly = false;
                if (matches != null && matches.Count > 0)
                {
                    foreach (Match match in matches)
                    {
                        TextSpan span = new TextSpan { StartPosition = match.Index, EndPosition = match.Index + match.Value.Length };
                        if ((span.StartPosition <= tagSpan.StartPosition && tagSpan.StartPosition < span.EndPosition) &&
                            (span.StartPosition < tagSpan.EndPosition && tagSpan.EndPosition <= span.EndPosition))
                        {
                            isNotStartTagOnly = true;
                            break;
                        }
                    }
                }
                if (!isNotStartTagOnly)
                {
                    List<TextSpan> excludeSpansForOnlyStartTag = GetExcludeSpan(target, false);
                    if (excludeSpansForOnlyStartTag != null && excludeSpansForOnlyStartTag.Count > 0)
                    {
                        if (!excludeSpansForOnlyStartTag.Exists(exclude =>
                        {
                            if ((exclude.StartPosition <= tagSpan.StartPosition && tagSpan.StartPosition < exclude.EndPosition) &&
                                (exclude.StartPosition < tagSpan.EndPosition && tagSpan.EndPosition <= exclude.EndPosition))
                                return true;
                            return false;
                        }))
                        {
                            lastStartElementTaskInfo = new SelectedTaskInfo();
                            lastStartElementTaskInfo.TaskName = "";
                            lastStartElementTaskInfo.TextSpan = tagSpan;
                            lastStartElementTaskInfo.SettingProperties = new List<string>();
                        }
                    }
                }
            }

            List<SelectedTaskInfo> result = new List<SelectedTaskInfo>();
            foreach (Match match in matches)
            {
                TextSpan span = new TextSpan { StartPosition = match.Index, EndPosition = match.Index + match.Value.Length };
                bool isExclude = false;
                if (excludeSpans != null && excludeSpans.Count > 0)
                {
                    isExclude = excludeSpans.Exists(exclude =>
                        {
                            if ((exclude.StartPosition <= span.StartPosition && span.StartPosition < exclude.EndPosition) &&
                                (exclude.StartPosition < span.EndPosition && span.EndPosition <= exclude.EndPosition))
                                return true;
                            return false;
                        });
                }
                if (isExclude)
                    continue;

                //get task name
                string taskName = match.Groups[Constants.StartElementPatternTaskNameGroupName].Value;
                //get end position
                string closeTag = match.Groups[Constants.StartElementPatternCloseTagGroupName].Value;
                int endPosition = 0;
                if (closeTag == ">")
                {
                    TextSpan endSpan = GetEndElementSpan(target, taskName, span.StartPosition, excludeSpans);
                    if (endSpan == null)
                        endPosition = target.Length;
                    else
                        endPosition = endSpan.EndPosition;
                }
                else
                    endPosition = span.EndPosition;

                //get property name list
                string propertiesText = match.Groups[Constants.StartElementPatternPropertiesTextGroupName].Value;
                List<string> properties = new List<string>();
                if (!string.IsNullOrWhiteSpace(propertiesText))
                    properties = GetPropertyNames(propertiesText);

                SelectedTaskInfo info = new SelectedTaskInfo();
                info.SettingProperties = properties;
                info.TaskName = taskName;
                info.TextSpan = new TextSpan { StartPosition = span.StartPosition, EndPosition = endPosition };
                result.Add(info);
            }
            if (lastStartElementTaskInfo != null)
            {
                result.Add(lastStartElementTaskInfo);
                result.Sort(
                    (a, b) =>
                    {
                        if (a.TextSpan.StartPosition < b.TextSpan.StartPosition)
                            return -1;
                        else if (a.TextSpan.StartPosition == b.TextSpan.StartPosition)
                        {
                            if (a.TextSpan.EndPosition < b.TextSpan.EndPosition)
                                return -1;
                            else if (a.TextSpan.EndPosition == b.TextSpan.EndPosition)
                                return 0;
                            else
                                return 1;
                        }
                        else
                            return 1;
                    });
            }
            return result;
        }

        private static List<TextSpan> GetExcludeSpan(string target, bool isStartElementPattern)
        {
            if (string.IsNullOrWhiteSpace(target))
                throw new ArgumentNullException();

            List<TextSpan> excludeSpans = new List<TextSpan>();
            //comment
            List<TextSpan> commentSpans = MSBuildMatch.GetMatchedSpans(target, Constants.CommentPattern);
            if (commentSpans != null && commentSpans.Count > 0)
                excludeSpans.AddRange(commentSpans);
            //CDATA
            List<TextSpan> cdataSpans = MSBuildMatch.GetMatchedSpans(target, Constants.CDATAPattern);
            if (cdataSpans != null && cdataSpans.Count > 0)
                excludeSpans.AddRange(cdataSpans);

            if (!isStartElementPattern)
            {
                //EndElement
                List<TextSpan> endElemSpans = MSBuildMatch.GetMatchedSpans(target, string.Format(Constants.EndElementPattern, "\\w*"));
                if (endElemSpans != null && endElemSpans.Count > 0)
                    excludeSpans.AddRange(endElemSpans);
                //xml declaration
                List<TextSpan> xmlDeclaretionSpans = MSBuildMatch.GetMatchedSpans(target, Constants.XmlDeclarationPattern);
                if (xmlDeclaretionSpans != null && xmlDeclaretionSpans.Count > 0)
                    excludeSpans.AddRange(xmlDeclaretionSpans);
            }

            return excludeSpans;
        }

        private static List<string> GetPropertyNames(string target)
        {
            if (string.IsNullOrWhiteSpace(target))
                throw new ArgumentNullException();

            List<string> properties = new List<string>();
            MatchCollection propMatches = MSBuildMatch.GetMatches(target, Constants.AttributesPattern, 0);
            if (propMatches == null || propMatches.Count <= 0)
                return null;
            foreach (Match propMatch in propMatches)
            {
                string propName = propMatch.Groups[Constants.AttributesPatternPropertyNameGroupName].Value;
                if (!string.IsNullOrWhiteSpace(propName))
                    properties.Add(propName);
            }
            return properties;
        }

        private static TextSpan GetEndElementSpan(string target, string taskName, int startElementPosition, List<TextSpan> excludeSpans)
        {
            if (string.IsNullOrWhiteSpace(target))
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(taskName))
                throw new ArgumentNullException();

            MatchCollection matches = MSBuildMatch.GetMatches(target, string.Format(Constants.EndElementPattern, taskName), startElementPosition);
            if (matches == null || matches.Count <= 0)
                return null;

            Match targetMatch = null;
            foreach (Match match in matches)
            {
                TextSpan span = new TextSpan { StartPosition = match.Index, EndPosition = match.Index + match.Value.Length };
                bool isExclude = false;
                if (excludeSpans != null && excludeSpans.Count > 0)
                {
                    isExclude = excludeSpans.Exists(exclude =>
                    {
                        if ((exclude.StartPosition <= span.StartPosition && span.StartPosition < exclude.EndPosition) &&
                            (exclude.StartPosition < span.EndPosition && span.EndPosition <= exclude.EndPosition))
                            return true;
                        return false;
                    });
                }
                if (!isExclude)
                {
                    targetMatch = match;
                    break;
                }
            }
            if (targetMatch == null)
                return null;
            else
                return new TextSpan { StartPosition = targetMatch.Index, EndPosition = targetMatch.Index + targetMatch.Value.Length };
        }
    }
}
